package com.hioove.core.container;

import com.alibaba.fastjson.JSONObject;
import com.hioove.core.filter.FilterChain;
import com.hioove.core.model.base.config.ClassConfig;
import com.hioove.core.model.base.config.ConfigurationInformation;
import com.hioove.core.model.base.config.StartClassConfig;
import com.hioove.core.util.Builder;
import com.hioove.core.util.Commons;
import com.hioove.core.web.model.ServletRequest;
import com.hioove.core.web.model.ServletResponse;
import com.hioove.core.web.mvc.route.Dispatch;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.MemoryAttribute;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.AsciiString;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;


public class NettyContainer implements IContainer {
    final static Logger log = LoggerFactory.getLogger(NettyContainer.class);
    static AtomicInteger HIOO_THREAD_INDEX = new AtomicInteger();

    private static ThreadPoolExecutor hiooveThread = new ThreadPoolExecutor(3, 8,
            2, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName(Commons.HIOOVE_THREAD_NAME + HIOO_THREAD_INDEX.getAndIncrement());
            return thread;
        }
    }, new ThreadPoolExecutor.CallerRunsPolicy());
    private StartClassConfig startupParam;
    private ConfigurationInformation configInfo;
    private HashMap<String, ClassConfig.MethodSupport> dispatch;
    private FilterChain filter;


    @Override
    public Object start() {

        ServerBootstrap b = new ServerBootstrap();
        EventLoopGroup acceptorLoopGroup = new NioEventLoopGroup(1);
        EventLoopGroup handlerLoopGroup = new NioEventLoopGroup(8);
        b.group(acceptorLoopGroup/*, handlerLoopGroup*/)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {

                        log.info("Handler: " + ch);
                        ch.pipeline()
                                .addLast(new ReadTimeoutHandler(600))
                                .addLast("decoder", new HttpRequestDecoder())
                                .addLast("encoder", new HttpResponseEncoder())
                                .addLast("aggregator", new HttpObjectAggregator(1 << 32))
                                .addLast("handler", new HttpHandler(getFilter(), getDispatch()));
                    }
                });
        ChannelFuture sync = null;
        try {
            sync = b.bind(Commons.SERVER_PORT).sync();
            sync.channel().closeFuture().sync();
        } catch (InterruptedException e) {
        }
        return null;
    }

    public static class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
        final HashMap<String, ClassConfig.MethodSupport> dispatch;
        final FilterChain filter;

        private AsciiString contentType = HttpHeaderValues.TEXT_PLAIN;

        public HttpHandler(FilterChain filter, HashMap<String, ClassConfig.MethodSupport> dispatch) {
            this.dispatch = dispatch;
            this.filter = filter;
        }

        // 容器核心代码
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
            // 自定义线程组客户端请求
//            hiooveThread.submit(() -> {
            FullHttpRequest req = (FullHttpRequest) msg;
            // 1.解析参数 分别定义 ServletRequest ServletResponse
            ServletRequest servletRequest = getParamsFromChannel(ctx, req);
            ServletResponse servletResponse = Builder.of(ServletResponse::new).build();
            // 2.先过滤器执行
            filter.setTask(() -> new Dispatch().start(servletRequest, servletResponse, dispatch));
            filter.doFilter(servletRequest, servletResponse, filter);
            // 3.处理结果
            Object contant = Optional.ofNullable(servletResponse.getContent()).orElse("");
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            ByteBuf byteBuf = Unpooled.copiedBuffer(contant.toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(byteBuf);
            HttpHeaders heads = response.headers();
            heads.add(CONTENT_TYPE, contentType + "; charset=UTF-8");
            heads.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
            heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
            byteBuf.release();
            ctx.write(response);
//            });
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            super.channelReadComplete(ctx);
            ctx.flush();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            Stream.of(ctx).filter(Objects::nonNull).peek(ChannelHandlerContext::close).count();
            Stream.of(cause).filter(Objects::nonNull).peek(Throwable::printStackTrace).count();
        }
    }

    @Override
    public IContainer setStartupParam(StartClassConfig startupParam) {
        this.startupParam = startupParam;
        return this;
    }

    @Override
    public IContainer setConfigInfo(ConfigurationInformation configInfo) {
        this.configInfo = configInfo;
        return this;
    }

    @Override
    public IContainer setDispatch(HashMap<String, ClassConfig.MethodSupport> dispatch) {
        this.dispatch = dispatch;
        return this;
    }

    @Override
    public IContainer setFilter(FilterChain filter) {
        this.filter = filter;
        return this;
    }

    public StartClassConfig getStartupParam() {
        return startupParam;
    }

    public ConfigurationInformation getConfigInfo() {
        return configInfo;
    }

    public HashMap<String, ClassConfig.MethodSupport> getDispatch() {
        return dispatch;
    }

    public FilterChain getFilter() {
        return filter;
    }

    private static ServletRequest getParamsFromChannel(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) {
        HttpHeaders headers = fullHttpRequest.headers();
        String strContentType = Optional.ofNullable(headers.get(HttpHeaderNames.CONTENT_TYPE)).orElse(Commons.NULL_CHARACTER).trim();
        Builder<HashMap<String, Object>> result = Builder.of(HashMap<String, Object>::new);
        // Get 请求
        Stream.of(fullHttpRequest).filter(httpRequest -> httpRequest.method().equals(HttpMethod.GET)).forEach(httpRequest -> {
            new QueryStringDecoder(httpRequest.uri()).parameters().forEach((key, value) -> result.with(HashMap::put, key, value.size() <= 1 ? value.stream().findFirst().get() : value));
        });
        // post 请求
        Stream.of(fullHttpRequest).filter(httpRequest -> httpRequest.method().equals(HttpMethod.POST)).forEach(httpRequest -> {
            log.debug("Requested content type!!{}", strContentType);
            new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), httpRequest).getBodyHttpDatas().stream()
                    .filter(interfaceHttpData -> interfaceHttpData.getHttpDataType().equals(InterfaceHttpData.HttpDataType.Attribute))
                    .forEach(interfaceHttpData -> {
                        result.with(HashMap::put, ((MemoryAttribute) interfaceHttpData).getName(), ((MemoryAttribute) interfaceHttpData).getValue());
                    });
            new QueryStringDecoder(httpRequest.uri()).parameters().forEach((key, value) -> result.with(HashMap::put, key, value.size() <= 1 ? value.stream().findFirst().get() : value));
        });
        AtomicReference<String> strContent = null;
        Stream.of(fullHttpRequest).filter(httpRequest -> httpRequest.method().equals(HttpMethod.POST)).forEach(httpRequest -> {
            log.debug("Requested content type!!{}", strContentType);
            ByteBuf content = httpRequest.content();
            byte[] reqContent = new byte[content.readableBytes()];
            content.readBytes(reqContent);
            strContent.set(new String(reqContent, StandardCharsets.UTF_8));
            JSONObject.parseObject(strContent.get()).forEach((k, v) -> {
                result.with(HashMap::put, k, v);
            });
        });
        return Builder.of(ServletRequest::new)
                .with(ServletRequest::setParam, result.build())
                .with(ServletRequest::setUrl, fullHttpRequest.uri().substring(0, fullHttpRequest.uri().indexOf(Commons.QUESTION_MARK)<0?fullHttpRequest.uri().length():fullHttpRequest.uri().indexOf(Commons.QUESTION_MARK)))
                .with(ServletRequest::setMethorType, fullHttpRequest.method().name())
                .with(ServletRequest::setHeaders, headers.entries())
                .with(ServletRequest::setContent, Objects.isNull(strContent) ? null : strContent.get())
                .build();
    }
}
